﻿using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Threading.Tasks;
using System.Threading;

namespace Moon.Sql
{
    public class OracleDatabase : DatabaseProvider, IDatabase
    {
        private string _ConnectionString = null;
        private ISqlDialect _SqlDialect = null;

        public OracleDatabase(string connectionString)
        {
            _ConnectionString = connectionString;
            _Connection = new OracleConnection(connectionString);
            _SqlDialect = new OracleDialect();
            DbId = $"ORACLE{DateTime.Now.Ticks}";
        }

        public IDbConnection Connection { get { return _Connection; } }
        public IDbTransaction Transaction { get { return _Transaction; } }
        public string ConnectionString { get { return _ConnectionString; } }
        public IAop Aop { get { return _Aop; } set { _Aop = value; } }
        public ISqlDialect SqlDialect { get { return _SqlDialect; } }
        public string DbId { get; private set; }
        public int CommandTimeout { get; set; } = 30;

        public override QueryPageResult<T> QueryPage<T>(string sql, object param = null)
        {
            OracleDynamicParameters dyParams = QueryPage(sql, out string newsql, param);
            return base.QueryPage<T>(newsql, dyParams);
        }

        public override async Task<QueryPageResult<T>> QueryPageAsync<T>(string sql, object param = null)
        {
            OracleDynamicParameters dyParams = QueryPage(sql, out string newsql, param);
            return await base.QueryPageAsync<T>(newsql, dyParams);
        }

        private OracleDynamicParameters QueryPage(string sql, out string newsql, object param = null)
        {
            newsql = "";
            if (string.IsNullOrWhiteSpace(sql)) return null;
            OracleDynamicParameters dyParams = new OracleDynamicParameters();
            if (param != null)
            {
                bool isDynamic = false;
                if ((isDynamic = CommonUtils.IsDynamicParameter(param)) || param is DynamicParameters)
                {
                    DynamicParameters dynamicParameters = null;
                    if (isDynamic) dynamicParameters = CommonUtils.GetDynamicParameters(param);
                    else dynamicParameters = param as DynamicParameters;
                    foreach (KeyValuePair<string, ParamInfo> item in dynamicParameters.Get())
                    {
                        dyParams.Add(item.Key, item.Value.Value);
                    }
                }
                else if (param is OracleDynamicParameters)
                {
                    dyParams = param as OracleDynamicParameters;
                }
                else
                {
                    throw new ArgumentNullException("parameter error");
                }
            }
            string[] array = sql.Split(';');
            if (array.Length < 2) return null;
            StringBuilder builder = new StringBuilder();
            builder.Append("BEGIN")
            .AppendLine(" OPEN :refCursor1 FOR ").Append(array[0]).Append(";")
            .AppendLine(" OPEN :refCursor2 FOR ").Append(array[1]).Append(";")
            .AppendLine("END;");

            dyParams.Add(":refCursor1", OracleDbType.RefCursor, ParameterDirection.Output);
            dyParams.Add(":refCursor2", OracleDbType.RefCursor, ParameterDirection.Output);

            OnExecuting(builder.ToString(), dyParams, ExecuteType.Query);
            newsql = builder.ToString();
            return dyParams;
        }

        public override int BulkInsert(DataTable table, string tableName)
        {
            throw new NotImplementedException("未实现");
        }

        public override Task<int> BulkInsertAsync(DataTable table, string tableName)
        {
            throw new NotImplementedException("未实现");
        }

        public int Execute(int count, string sql, IDynamicParameters dyParams)
        {
            int rowCount = 0;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, null, ExecuteType.NonQuery);

            bool wasClosed = _Connection.State == ConnectionState.Closed;
            try
            {
                if (wasClosed) _Connection.Open();
                using (OracleCommand cmd = GetCommand(sql, null) as OracleCommand)
                {
                    cmd.ArrayBindCount = count;
                    cmd.FetchSize = 1000;
                    dyParams.AddParameters(cmd);
                    rowCount = cmd.ExecuteNonQuery();
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                if (wasClosed) _Connection.Close();
                OnExecuted(executingArgs, exception, rowCount);
            }
            return rowCount;
        }

        public async Task<int> ExecuteAsync(int count, string sql, IDynamicParameters dyParams)
        {
            int rowCount = 0;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, null, ExecuteType.NonQuery);

            bool wasClosed = _Connection.State == ConnectionState.Closed;
            try
            {
                if (wasClosed) _Connection.Open();
                using (OracleCommand cmd = GetCommand(sql, null) as OracleCommand)
                {
                    cmd.ArrayBindCount = count;
                    cmd.FetchSize = 1000;
                    dyParams.AddParameters(cmd);
                    rowCount = await cmd.ExecuteNonQueryAsync();
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                if (wasClosed) _Connection.Close();
                OnExecuted(executingArgs, exception, rowCount);
            }
            return rowCount;
        }

        protected override IDbCommand GetCommand(string sql, object param = null, CommandType? commandType = null, IDbTransaction transaction = null)
        {
            OracleCommand cmd = new OracleCommand(sql, _Connection as OracleConnection);
            //设置按照变量名绑定参数
            cmd.BindByName = true;
            cmd.Transaction = transaction as OracleTransaction;
            cmd.CommandType = commandType == null ? CommandType.Text : (CommandType)commandType;
            cmd.CommandTimeout = CommandTimeout;
            if (param != null)
            {
                SetupParam(cmd, param);
            }
            return cmd;
        }

        protected override IDataAdapter GetAdapter(IDbCommand command)
        {
            return new OracleDataAdapter(command as OracleCommand);
        }

        ~OracleDatabase() => this.Dispose();
        int _disposeCounter;
        public void Dispose()
        {
            if (Interlocked.Increment(ref _disposeCounter) != 1) return;
            _Connection?.Close();
        }
    }
}
